#include "Kernel.h"
#include "Multiboot.h"
#include "Hal/IHardware.h"
#include "Memory/MemoryManager.h"
#include "Drivers/Console.h"
#include "Drivers/Keyboard.h"
#include "Scheduler/Scheduler.h"
#include "Utils/String.h"
#include "Shell.h"

#define KERNEL_TRACE(x ...)         TRACE("Kernel: " x)
#define KERNEL_ARGS_SIGNATURE       0x1EA7BEEF
#define MULTIBOOT_ARGS_SIGNATURE    0x2BADB002

extern "C"
{
    extern uint32 Kernel_BootSignature;
    extern void * Kernel_BootArgs;

	void Kernel_Start( void );
}

CKernel sKernel;
CKernel * Kernel = &sKernel;

void Kernel_Start( void )
{
	Hardware->ForceLock();
	Kernel->Initialize();

	Hardware->ForceUnlock();
	Kernel->Run();

	Hardware->ForceLock();
	Kernel->Shutdown();
}

void CKernel::Initialize( void )
{
	Debugger->Initialize();

	KERNEL_TRACE( "Initializing...\n" );
	_ParseArgs();

	Heap->Initialize();
	Hardware->Initialize();
	Console->Initialize();
	Keyboard->Initialize( fKernelArgs.fKeymap );
	Scheduler::Initialize();
	Shell::Initialize( &sConsole );
}


void CKernel::_ParseArgs()
{
    Memory_Set( &fKernelArgs, 0, sizeof(KernelArgs_t) );

	if( Kernel_BootArgs == NULL )
        return;

    KernelArgs_t * args = NULL;

    if( Kernel_BootSignature == MULTIBOOT_ARGS_SIGNATURE )
        args = Multiboot_PrepareArgs( (MultibootInfo_t *)Kernel_BootArgs );

    else if ( Kernel_BootSignature == KERNEL_ARGS_SIGNATURE )
        args = (KernelArgs_t *)Kernel_BootArgs;

    if( args == NULL )
        return;

    Memory_Copy( &fKernelArgs, args, sizeof(KernelArgs_t) );

#if 1
    KERNEL_TRACE( "Args Received... %p\n", args );
    KERNEL_TRACE( "Memory: Lower=%u, Upper=%u\n", args->fMemory.fLower * 1024, args->fMemory.fUpper * 1024 );

    if( args->fCommandLine != NULL )
        KERNEL_TRACE( "Command Line: %s\n", args->fCommandLine );

    if( args->fModules.fCount > 0 )
    {
        KERNEL_TRACE( "Modules: %i\n", args->fModules.fCount );

        KernelModule_t * module = args->fModules.fFirst;

        for( uint32 i = 0; i < args->fModules.fCount && module != NULL; i++ )
        {
            KERNEL_TRACE( "\tStart=%p, Length=%u, Identifier=%s\n", module->fData, module->fLength, module->fIdentifier );
            module = module->fNext;
        }
    }
#endif

    // find keymap
    for( KernelModule_t * module = args->fModules.fFirst; module != NULL; module = module->fNext )
    {
        if( StringCompare( "/Mint/System/Keymaps/", module->fIdentifier, true, 21 ) )
            continue;

        args->fKeymap = (uint16 *)module->fData;
        break;
    }
}


void CKernel::Run( void )
{
	KERNEL_TRACE( "Running...\n" );
	sConsole.Write( "\n  Welcome to \xFB\xF3Mi\xF2nt\xFF...\n" );

	sConsole.Write( "\n Running tests...\n" );

	const char * charArray = CString::Format( "%i, %s!", 1337, "Hello World" );
	Size length = CString::Length( charArray );

	ASSERT( CString::Compare( charArray, length, "1337, Hello World!", 18 ) == 0 );

	CString string( charArray );

	ASSERT( string.Compare( charArray, length ) == 0 );
	ASSERT( string.ToInt32() == 1337 );

	string.FromFormat( "%s", NULL );
	ASSERT( string.Compare( "<null>", 6 ) == 0 );

	Shell::Run();

    for( int i = 2; i < 80; i++ )
    {
        Thread * thread = new Thread( "Idle Thread", __Idle, (void *)( i * 2 ) );
        Scheduler::StartThread( thread );
    }

	while( Scheduler::GetThreadCount() > 0 )
		Scheduler::Yield();
}


void CKernel::Shutdown( void )
{
	KERNEL_TRACE( "Shutting down...\n" );
	Shell::Shutdown();
}


void CKernel::Panic( const char * message, ... )
{
	Hardware->ForceLock();

	KERNEL_TRACE( "\n\n\n!!! Panic !!!\n\n" );

	Console->SetForeground( CConsole::ColorBrightWhite );
	Console->SetBackground( CConsole::ColorRed );
	Console->Clear();
	Console->SetPosition( 2, 1 );
	Console->Write( "MintOS - Kernel Panic\n\n" );

	Args args;
	Args_Start( args, message );

	Console->SetPosition( 2, 3 );
	Console->Write( message, args );
	Debugger->Write( message, args );

	Args_End( args );

	Console->SetPosition( 2, 6 );
	Console->Write( "Context:\n" );

	Hardware->DumpCurrentContext( Console );
	Hardware->DumpCurrentContext( Debugger );

	Console->SetPosition( 2, Console->GetHeight() - 2 );
	Console->Write( "System halting...\n" );

	for(;;)
		;
}
